package tech.lq0.dreamaticvoyage.block.entity;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.lq0.dreamaticvoyage.block.machine.fukamizutech.tier2.FukamizuFermenter;
import tech.lq0.dreamaticvoyage.capability.ModCapabilities;
import tech.lq0.dreamaticvoyage.capability.uce.UCEnergyStorage;
import tech.lq0.dreamaticvoyage.gui.menu.FukamizuFermenterMenu;
import tech.lq0.dreamaticvoyage.gui.slot.ContainerEnergyData;
import tech.lq0.dreamaticvoyage.init.BlockEntityRegistry;
import tech.lq0.dreamaticvoyage.network.DmvNetwork;
import tech.lq0.dreamaticvoyage.network.packet.FukamizuFermenterSyncPacket;
import tech.lq0.dreamaticvoyage.recipe.FukamizuFermentingRecipe;
import tech.lq0.dreamaticvoyage.recipe.SimpleFluidContainer;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class FukamizuFermenterBlockEntity extends BlockEntity implements WorldlyContainer, MenuProvider {

    protected static final int SLOT_INPUT = 0;
    protected static final int SLOT_YEAST = 1;
    protected static final int SLOT_FLUID_INPUT = 2;
    protected static final int SLOT_FLUID_OUTPUT = 3;

    public static final int MAX_DATA_COUNT = 3;
    public static final int MAX_ENERGY = 25600;
    public static final int DEFAULT_ENERGY_COST = 16;
    public static final int MAX_FLUID_CAPACITY = 8000;
    public static final int MAX_FLUID_TRANSFER = 8;

    protected NonNullList<ItemStack> items = NonNullList.withSize(4, ItemStack.EMPTY);
    private LazyOptional<?>[] itemHandlers = SidedInvWrapper.create(this, Direction.NORTH, Direction.DOWN);
    private LazyOptional<UCEnergyStorage> energyHandler;
    public LazyOptional<FluidTank> inputTank;
    public LazyOptional<FluidTank> outputTank;

    private FluidStack tempFluid = FluidStack.EMPTY;
    private int tempCapacity = 1000;

    // Only used on the client
    public FluidStack clientInputFluid = FluidStack.EMPTY;
    public FluidStack clientOutputFluid = FluidStack.EMPTY;

    protected final ContainerEnergyData dataAccess = new ContainerEnergyData() {
        public long get(int pIndex) {
            return switch (pIndex) {
                case 0 -> {
                    AtomicInteger energy = new AtomicInteger();
                    FukamizuFermenterBlockEntity.this.getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(consumer -> energy.set(consumer.getEnergyStored()));
                    yield energy.get();
                }
                case 1 -> FukamizuFermenterBlockEntity.this.tempFluid.getAmount();
                case 2 -> FukamizuFermenterBlockEntity.this.tempCapacity;
                default -> 0;
            };
        }

        public void set(int pIndex, long pValue) {
            if (pIndex == 0) {
                FukamizuFermenterBlockEntity.this.getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(consumer -> consumer.receiveEnergy((int) pValue, false));
            }
        }

        public int getCount() {
            return MAX_DATA_COUNT;
        }
    };

    public FukamizuFermenterBlockEntity(BlockPos pPos, BlockState pBlockState) {
        super(BlockEntityRegistry.FUKAMIZU_FERMENTER_BLOCK_ENTITY.get(), pPos, pBlockState);

        this.energyHandler = LazyOptional.of(() -> new UCEnergyStorage(MAX_ENERGY));
        this.inputTank = LazyOptional.of(() -> new FluidTank(MAX_FLUID_CAPACITY));
        this.outputTank = LazyOptional.of(() -> new FluidTank(MAX_FLUID_CAPACITY));
    }

    public static void serverTick(Level level, BlockPos blockPos, BlockState blockState, FukamizuFermenterBlockEntity blockEntity) {
        DmvNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(),
                new FukamizuFermenterSyncPacket(blockPos,
                        blockEntity.inputTank.map(FluidTank::getFluid).orElse(FluidStack.EMPTY),
                        blockEntity.outputTank.map(FluidTank::getFluid).orElse(FluidStack.EMPTY))
        );

        if (blockEntity.hasRecipe()) {
            blockEntity.craftItem();
        }

        blockEntity.getItem(SLOT_FLUID_INPUT).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresent(handler -> {
            if (blockEntity.getItem(SLOT_FLUID_OUTPUT) == ItemStack.EMPTY) {
                int filled = handler.fill(blockEntity.outputTank.map(FluidTank::getFluid).orElse(FluidStack.EMPTY), IFluidHandler.FluidAction.EXECUTE);
                blockEntity.outputTank.ifPresent(tank -> tank.drain(filled, IFluidHandler.FluidAction.EXECUTE));

                blockEntity.setItem(SLOT_FLUID_OUTPUT, handler.getContainer());
                blockEntity.setItem(SLOT_FLUID_INPUT, ItemStack.EMPTY);

                blockEntity.setChanged();
            }
        });

        blockEntity.fillOutput();
    }

    private void fillOutput() {
        if (this.tempFluid.getAmount() == 0) {
            this.tempFluid = FluidStack.EMPTY;
        }

        if (this.energyHandler.map(UCEnergyStorage::getEnergyStored).orElse(0) < DEFAULT_ENERGY_COST) return;

        if (this.tempFluid.isEmpty()) return;

        this.outputTank.ifPresent(handler -> {
            FluidStack fluidStack = new FluidStack(tempFluid, Math.min(tempFluid.getAmount(), MAX_FLUID_TRANSFER));
            int filled = handler.fill(fluidStack, IFluidHandler.FluidAction.EXECUTE);
            this.tempFluid.shrink(filled);
        });
        this.energyHandler.ifPresent(handler -> handler.extractEnergy(DEFAULT_ENERGY_COST, false));
        this.setChanged();
    }

    private void craftItem() {
        Optional<FukamizuFermentingRecipe> recipe = getCurrentRecipe();
        if (recipe.isEmpty()) {
            return;
        }

        if (this.energyHandler.map(UCEnergyStorage::getEnergyStored).orElse(0) < DEFAULT_ENERGY_COST) return;

        ItemStack input = this.items.get(SLOT_INPUT);
        input.shrink(1);
        ItemStack yeast = this.items.get(SLOT_YEAST);
        yeast.shrink(1);

        this.inputTank.ifPresent(handler -> handler.drain(recipe.get().getInput().getAmount(), IFluidHandler.FluidAction.EXECUTE));
        this.tempFluid = recipe.get().getOutput();
        this.tempCapacity = this.tempFluid.getAmount();
        this.setChanged();
    }

    private Optional<FukamizuFermentingRecipe> getCurrentRecipe() {
        if (this.level == null) {
            return Optional.empty();
        }
        if (!this.tempFluid.isEmpty()) {
            return Optional.empty();
        }

        SimpleFluidContainer inventory = new SimpleFluidContainer(2, 1);
        inventory.setItem(0, this.items.get(SLOT_INPUT));
        inventory.setItem(1, this.items.get(SLOT_YEAST));
        this.inputTank.ifPresent(handler -> inventory.setFluid(0, handler.getFluid()));

        return this.level.getRecipeManager().getRecipeFor(FukamizuFermentingRecipe.Type.INSTANCE, inventory, level);
    }

    private boolean hasRecipe() {
        Optional<FukamizuFermentingRecipe> recipe = getCurrentRecipe();

        if (recipe.isEmpty()) {
            return false;
        }

        if (getLevel() == null) {
            return false;
        }

        FluidStack result = recipe.get().getOutput();

        AtomicBoolean flag = new AtomicBoolean(false);
        this.outputTank.ifPresent(handler -> flag.set(
                handler.getFluid().isEmpty() ||
                        (handler.getFluid().isFluidEqual(result) && handler.getSpace() >= result.getAmount())));
        return flag.get();
    }

    @Override
    public void load(CompoundTag pTag) {
        super.load(pTag);

        this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
        ContainerHelper.loadAllItems(pTag, this.items);
        if (pTag.contains("UmisuEnergy")) {
            getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(handler -> ((UCEnergyStorage) handler).deserializeNBT(pTag.get("UmisuEnergy")));
        }
        if (pTag.contains("InputFluid")) {
            this.inputTank.ifPresent(handler -> handler.readFromNBT(pTag.getCompound("InputFluid")));
        }
        if (pTag.contains("OutputFluid")) {
            this.outputTank.ifPresent(handler -> handler.readFromNBT(pTag.getCompound("OutputFluid")));
        }
        this.tempFluid = FluidStack.loadFluidStackFromNBT(pTag);
        if (pTag.contains("TempCapacity")) {
            this.tempCapacity = pTag.getInt("TempCapacity");
        } else {
            this.tempCapacity = 1000;
        }
    }

    @Override
    protected void saveAdditional(CompoundTag pTag) {
        super.saveAdditional(pTag);

        ContainerHelper.saveAllItems(pTag, this.items);
        getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(handler -> pTag.put("UmisuEnergy", ((UCEnergyStorage) handler).serializeNBT()));
        this.inputTank.ifPresent(handler -> {
            CompoundTag fluidTag = new CompoundTag();
            handler.writeToNBT(fluidTag);
            pTag.put("InputFluid", fluidTag);
        });
        this.outputTank.ifPresent(handler -> {
            CompoundTag fluidTag = new CompoundTag();
            handler.writeToNBT(fluidTag);
            pTag.put("OutputFluid", fluidTag);
        });
        this.tempFluid.writeToNBT(pTag);
        pTag.putInt("TempCapacity", this.tempCapacity);
    }

    @Override
    public int[] getSlotsForFace(Direction pSide) {
        if (pSide == Direction.DOWN) {
            return new int[]{SLOT_FLUID_OUTPUT};
        } else if (pSide == Direction.UP) {
            return new int[]{SLOT_INPUT, SLOT_FLUID_INPUT};
        } else {
            return new int[]{SLOT_YEAST};
        }
    }

    @Override
    public boolean canPlaceItemThroughFace(int pIndex, ItemStack pItemStack, @Nullable Direction pDirection) {
        return this.canPlaceItem(pIndex, pItemStack);
    }

    @Override
    public boolean canPlaceItem(int pIndex, ItemStack pStack) {
        if (pIndex == 3) {
            return false;
        } else if (pIndex == 2 && pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).isPresent()) {
            return true;
        } else return pIndex == 0 || pIndex == 1;
    }

    @Override
    public boolean canTakeItemThroughFace(int pIndex, ItemStack pStack, Direction pDirection) {
        return pIndex == 3 && pDirection == Direction.DOWN;
    }

    @Override
    public int getContainerSize() {
        return this.items.size();
    }

    @Override
    public boolean isEmpty() {
        for (ItemStack itemstack : this.items) {
            if (!itemstack.isEmpty()) {
                return false;
            }
        }

        return true;
    }

    @Override
    public ItemStack getItem(int pSlot) {
        return this.items.get(pSlot);
    }

    @Override
    public ItemStack removeItem(int pSlot, int pAmount) {
        return ContainerHelper.removeItem(this.items, pSlot, pAmount);
    }

    @Override
    public ItemStack removeItemNoUpdate(int pSlot) {
        return ContainerHelper.takeItem(this.items, pSlot);
    }

    @Override
    public void setItem(int pSlot, ItemStack pStack) {
        ItemStack itemstack = this.items.get(pSlot);
        boolean flag = !pStack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, pStack);
        this.items.set(pSlot, pStack);
        if (pStack.getCount() > this.getMaxStackSize()) {
            pStack.setCount(this.getMaxStackSize());
        }

        if ((pSlot == 0 || pSlot == 1 || pSlot == 2) && !flag) {
            this.setChanged();
        }
    }

    @Override
    public boolean stillValid(Player pPlayer) {
        return Container.stillValidBlockEntity(this, pPlayer);
    }

    @Override
    public void clearContent() {
        this.items.clear();
    }

    @Override
    public Component getDisplayName() {
        return Component.translatable("container.dreamaticvoyage.fukamizu_fermenter");
    }

    @Nullable
    @Override
    public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) {
        return new FukamizuFermenterMenu(pContainerId, pPlayerInventory, this, this.dataAccess);
    }

    @Override
    public <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
        if (cap == ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY) {
            return energyHandler.cast();
        }

        // 不处理正面
        if (side == this.getBlockState().getValue(FukamizuFermenter.FACING)) return super.getCapability(cap, side);

        if (!this.remove && side != null && cap == ForgeCapabilities.ITEM_HANDLER) {
            if (side == Direction.DOWN) {
                return itemHandlers[1].cast();
            } else {
                return itemHandlers[0].cast();
            }
        }

        if (cap == ForgeCapabilities.FLUID_HANDLER) {
            if (side == Direction.DOWN) {
                return outputTank.cast();
            }
            return inputTank.cast();
        }

        return super.getCapability(cap, side);
    }

    @Override
    public void invalidateCaps() {
        super.invalidateCaps();
        for (LazyOptional<?> itemHandler : itemHandlers) itemHandler.invalidate();
        this.energyHandler.invalidate();
        this.inputTank.invalidate();
        this.outputTank.invalidate();
    }

    @Override
    public void reviveCaps() {
        super.reviveCaps();
        this.itemHandlers = SidedInvWrapper.create(this, Direction.NORTH, Direction.DOWN);
        this.energyHandler = LazyOptional.of(() -> new UCEnergyStorage(MAX_ENERGY));
        this.inputTank = LazyOptional.of(() -> new FluidTank(MAX_FLUID_CAPACITY));
        this.outputTank = LazyOptional.of(() -> new FluidTank(MAX_FLUID_CAPACITY));
    }
}
